package com.qingfeng.medical.biz.information.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qingfeng.currency.dozer.DozerUtils;
import com.qingfeng.medical.base.handler.GeneralAuthException;
import com.qingfeng.medical.base.result.ResultCodeEnum;
import com.qingfeng.medical.biz.information.dao.DictMapper;
import com.qingfeng.medical.biz.information.domain.dto.DictSaveDTO;
import com.qingfeng.medical.biz.information.domain.dto.DictUpdateDTO;
import com.qingfeng.medical.biz.information.domain.entity.Dict;
import com.qingfeng.medical.biz.information.domain.vo.DictExcelVo;
import com.qingfeng.medical.biz.information.domain.vo.DictVo;
import com.qingfeng.medical.biz.information.listener.DictExcelListener;
import com.qingfeng.medical.biz.information.service.DictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 数据字典表
 *
 * @author 清风学Java
 * @date 2023-04-08 23:43:31
 */
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {

    @Autowired
    private DozerUtils dozerUtils;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 查询数据字典
     *
     * @return
     */
    @Override
    public List<DictVo> findListTree() {
        try {
            // redis中存在就直接返回
            String dicts = stringRedisTemplate.boundValueOps("dict").get();
            if (StrUtil.isNotBlank(dicts)) {
                return objectMapper.readValue(dicts, objectMapper.getTypeFactory()
                        .constructParametricType(ArrayList.class, DictVo.class)
                );
            }

            // 查询数据库
            List<Dict> dictList = baseMapper.selectList(null);
            //2、组装成父子的树形结构
            //2.1 找到所有的一级分类
            List<DictVo> dictVoList = dictList.stream()
                    .filter(d -> d.getParentId() == 0)
                    .map(dict ->
                            //将上一步的结果通过map映射再一次进行数据操作
                            //map操作，把当前分类转换成一个新的对象  dict 就是过滤出的一个个dict对象
                            dozerUtils.map2(dict, DictVo.class)
                                    .setChildren(getChildrens(dict, dictList))
                    ).collect(Collectors.toList());

            stringRedisTemplate.boundValueOps("dict")
                    .set(objectMapper.writeValueAsString(dictVoList));

            return dictVoList;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new GeneralAuthException(ResultCodeEnum.FAIL.getCode(), e.getMessage());
        }
    }

    /**
     * 保存数据字典信息
     *
     * @param dictSaveDTO
     */
    @Override
    public void saveDict(DictSaveDTO dictSaveDTO) {
        // 排查同级下面是否有相同名字和相同编码的内容
        Dict dict = dozerUtils.map2(dictSaveDTO, Dict.class);
        checkDict(dict);
        baseMapper.insert(dict);

        //更新数据字典对应的值
        dict.setDictValue(String.valueOf(dict.getId()));
        baseMapper.updateById(dict);

        // 清除redis缓存
        stringRedisTemplate.delete(Arrays.asList("dict", "dictExcel"));
    }

    /**
     * 修改数据字典信息
     *
     * @param dictUpdateDTO
     */
    @Override
    public void updateDictById(DictUpdateDTO dictUpdateDTO) {
        // 排查同级下面是否有相同名字和相同编码的内容
        Dict dictEntity = dozerUtils.map2(dictUpdateDTO, Dict.class);
        checkDict(dictEntity);
        baseMapper.updateById(dictEntity);
        // 清除redis缓存
        stringRedisTemplate.delete(Arrays.asList("dict", "dictExcel"));
    }

    /**
     * 导出数据字典模板
     *
     * @param response
     */
    @Override
    public void exportTemplate(HttpServletResponse response) {
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            //设置URLEncoder.encode可以解决中文乱码问题   这个和EasyExcel没有关系
            response.setHeader("Content-Disposition",
                    "attachment;filename=" +
                            URLEncoder.encode("数据字典列表模板", "UTF-8") +
                            ".xlsx");

            // 调用方法实现写操作
            EasyExcel.write(response.getOutputStream(), DictExcelVo.class)
                    .sheet("数据字典列表")
                    .doWrite(Collections.emptyList());
        } catch (Exception e) {
            throw new GeneralAuthException(ResultCodeEnum.FAIL.getCode(), "数据字典模板导出异常，请重试！");
        }

    }

    /**
     * 导出数据字典
     *
     * @param response
     */
    @Override
    public void exportDict(HttpServletResponse response) {
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            //设置URLEncoder.encode可以解决中文乱码问题   这个和EasyExcel没有关系
            response.setHeader("Content-Disposition",
                    "attachment;filename=" +
                            URLEncoder.encode("数据字典列表", "UTF-8") +
                            ".xlsx");

            String dictExcel = stringRedisTemplate.boundValueOps("dictExcel").get();
            List<DictExcelVo> dictExcelVos;
            if (StrUtil.isBlank(dictExcel)) {
                //查询dict列表
                dictExcelVos = dozerUtils.mapList(baseMapper.selectList(null), DictExcelVo.class);
                stringRedisTemplate.boundValueOps("dictExcel").set(objectMapper.writeValueAsString(dictExcelVos));
            } else {
                dictExcelVos = objectMapper.readValue(dictExcel, objectMapper.getTypeFactory()
                        .constructParametricType(ArrayList.class, DictExcelVo.class)
                );
            }

            // 调用方法实现写操作
            EasyExcel.write(response.getOutputStream(), DictExcelVo.class)
                    .sheet("数据字典列表")
                    .doWrite(dictExcelVos);
        } catch (Exception e) {
            throw new GeneralAuthException(ResultCodeEnum.FAIL.getCode(), "数据字典导出异常，请重试！");
        }
    }

    /**
     * 导入数据字典
     *
     * @param file
     */
    @Override
    @Transactional(rollbackFor = GeneralAuthException.class)
    public void importDict(MultipartFile file) {
        try {
            EasyExcel.read(file.getInputStream(),
                            DictExcelVo.class,
                            new DictExcelListener(
                                    this,
                                    dozerUtils
                            )
                    )
                    .sheet()
                    .doRead();
        } catch (IOException e) {
            throw new GeneralAuthException(ResultCodeEnum.FAIL.getCode(), "数据字典导入异常，请重试！");
        }

        stringRedisTemplate.delete(Arrays.asList("dict", "dictExcel"));
    }

    /**
     * 根据数据Id查询子数据列表
     *
     * @param id
     * @return
     */
    @Override
    public List<Dict> findChildData(Long id) {
        //因为是继承自baseMapper，所以可以直接使用
        List<Dict> dictList = baseMapper.selectList(
                new LambdaQueryWrapper<Dict>()
                        .eq(Dict::getParentId, id)
        );
        for (Dict dict : dictList) {
            Long dictId = dict.getId();
            boolean isChild = this.isChildren(dictId);
            dict.setHasChildren(isChild);
        }
        return dictList;
    }


    /**
     * 判断id下面是否有子节点
     *
     * @param id
     * @return
     */
    private boolean isChildren(Long id) {
        Integer count = baseMapper.selectCount(
                new LambdaQueryWrapper<Dict>()
                        .eq(Dict::getParentId, id)
        );
        return count > 0;
    }

    /**
     * 递归封装出所有的子集
     *
     * @param dict
     * @param dictList
     * @return
     */
    private List<DictVo> getChildrens(Dict dict, List<Dict> dictList) {
        // 过滤所有菜单的父Id等于当前菜单的id
        return dictList.stream()
                .filter(d -> d.getParentId().equals(dict.getId()))
                .map(m ->
                        // 找到子集
                        dozerUtils.map2(m, DictVo.class)
                                .setChildren(getChildrens(m, dictList))
                ).collect(Collectors.toList());
    }

    private void checkDict(Dict dict) {
        Dict dictEntity = baseMapper.selectOne(
                new LambdaQueryWrapper<Dict>()
                        .like(Dict::getDictName, dict.getDictName()));

        Optional.ofNullable(dictEntity)
                .ifPresent(d -> {
                    throw new GeneralAuthException(ResultCodeEnum.FAIL.getCode(), "同级数据字典名字不能相同！");
                });
        // id为null 再排查编码是否是唯一的
        if (ObjectUtil.isEmpty(dict.getId())) {

            List<Dict> dictList = baseMapper.selectList(
                    new LambdaQueryWrapper<Dict>()
                            .eq(Dict::getDictCode, dict.getDictCode()));
            if (CollUtil.isNotEmpty(dictList)) {
                throw new GeneralAuthException(ResultCodeEnum.FAIL.getCode(), "编码必须是唯一标识且大写");
            }
        }
    }
}